'use strict'

module.exports = function() {
    let um = require('..');
    let base = require('./base');

    let mongoose = require('mongoose');

    let typeMapper = {
        Id: String,
        Text: String,
        Integer: Number,
        Double: Number,
        DateTime: Date,
        Bool: Boolean,
        UObject: mongoose.Schema.Types.ObjectId
    }

    function toMongoType(fieldType) {
        return typeMapper[fieldType];
    }

    class Dao extends base.Dao {
        constructor(session, uniqueModel) {
            super(session);

            let modelName = uniqueModel._meta.name;
            let uniqueSchema = uniqueModel._meta.schema;

            this.schema = this.fromUniqueSchema(uniqueSchema);
            this.mongoModel = mongoose.model(modelName, this.schema);
            this.uniqueModel = uniqueModel;
        }

        fromUniqueSchema(uniqueSchema) {
            let schemaDescription = {};
            for ( let fieldName in uniqueSchema ) {
                let fieldType = uniqueSchema[fieldName];
                if ( fieldType._name === 'UObjectArray' ) {
                    schemaDescription[fieldName] = [ mongoose.Schema.Types.ObjectId ];
                }
                else {
                    schemaDescription[fieldName] = toMongoType(fieldType._name);
                }
            }

            return new mongoose.Schema(schemaDescription);
        }

        executeAsync(operation, model, callback) {
            this.mongoModel[operation](model, function(err, result) {
                callback(err, result);
            });
        }

        getObjectIdKey() {
            return '_id';
        }

        // override
        createAsync(backendModel, callback) {
            this.executeAsync('create', backendModel, callback);
        }

        insertManyAsync(plainObjects, callback) {
            this.executeAsync('insertMany', plainObjects, callback);
        }

        countAsync(condition, callback) {
            this.mongoModel.where(condition).count(function(err, count) {
                callback(err, count);
            });
        }

        // override
        distinctAsync(field, condition, callback) {
            this.mongoModel.distinct(field, condition, function(err, result) {
                callback(err, result);
            });
        }

        aggregateAsync(condition, callback) {
            this.mongoModel.aggregate(condition, function(err, result) {
                callback(err, result);
            });
        }

        // override
        findAsync(condition, callback) {
            var sort = null;
            var skip = 0;
            var limit = 0;

            if ( '$sort' in condition ) {
                sort = condition.$sort;
                delete condition.$sort;
            }

            if ( '$skip' in condition ) {
                skip = Number(condition.$skip);
                delete condition.$skip;
            }

            if ( '$limit' in condition ) {
                limit = Number(condition.$limit);
                delete condition.$limit;
            }

            var query = this.mongoModel.find(condition);

            if ( sort ) {
                query.sort(sort);
            }

            if ( skip ) {
                query.skip(skip);
            }

            if ( limit ) {
                query.limit(limit);
            }

            query.exec(function(err, result) {
                callback(err, result);
            });
        }

        // override
        findOneAsync(condition, callback) {
            this.executeAsync('findOne', condition, callback);
        }

        // override
        updateAsync(condition, doc, options, callback) {
            if ( !callback ) {
                callback = options;
                this.mongoModel.update(condition, doc, function(err, result) {
                    callback(err, result);
                });
            }
            else {
                this.mongoModel.update(condition, doc, options, function(err, result) {
                    callback(err, result);
                });
            }
        }

        // override
        updateOneAsync(condition, doc, options, callback) {
            if ( !callback ) {
                callback = options;
                this.mongoModel.findOneAndUpdate(condition, doc, function(err, result) {
                    callback(err, result);
                });
            }
            else {
                this.mongoModel.findOneAndUpdate(condition, doc, options, function(err, result) {
                    callback(err, result);
                });
            }
        }

        // override
        removeAsync(condition, callback) {
            this.executeAsync('remove', condition, callback);
        }
    }

    class Session extends base.Session {
        constructor(options) {
            super(options);

            let uri = options.uri;
            let mongoOptions = options.options;

            mongoose.connect(uri, mongoOptions);

            this.mongo = mongoose;
            this.db = mongoose.connection;
            this.db.on('error', console.error.bind(console, 'connection error:'));
        }

        // override
        createDao(model) {
            return new Dao(this, model);
        }
    }

    return {
        Session
    }
}();
